﻿/*
 
Celerity: Sensory Overload
Copright 2012 by Adam Hill, David Gannon, Thomas Tamblyn & Patrick Ytting
Contact: adam.hill@outlook.com
Website: adamhill.org
Licence: MS-Pl Licence
  
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Celerity.Logic;
using Celerity.World.Particles;

namespace Celerity.World
{
	public class ObstacleManager
	{
		Logic.GameLogic logic;
		EmitterFactory emitter;

		public List<Cube> cubes;
		private List<Vector4> colours;
		private float currentPhase;
		private float deltaPhase;
		private int numTunnelCells;
		private Random rand;
		private float cellSize;
		public Effect effect;
		public float tunnelRadius;

		public int spawnDistance; // in tunnel cell units
		public float distanceTravelled;
		public int cellsTravelled;
		public int spawnFreq;
		private int lastCubePos;
		private int[] chaosLevels;
		private int numChaosLevels;

		public float chaos;

		public delegate void CubeCreatedHandler(object sender, CubeCreatedEventArgs e);
		public class CubeCreatedEventArgs : EventArgs
		{
			public float angle;
			public float z;
			public float tunnelCellSize;
			public float tunnelNumSegments;
			public int boxAngleIndex;
			public CubeCreatedEventArgs(float angle, float z, float tunnelCellSize, float tunnelNumSegments, int boxAngleIndex)
			{
				this.angle = angle;
				this.z = z;
				this.tunnelCellSize = tunnelCellSize;
				this.tunnelNumSegments = tunnelNumSegments;
				this.boxAngleIndex = boxAngleIndex;
			}
		}
		public event CubeCreatedHandler CubeCreated;
		protected void OnCubeCreated(object sender, CubeCreatedEventArgs e)
		{
			if (CubeCreated != null)
			{
				CubeCreated(this, e);
			}
		}

		public delegate void CubeRemovedHandler(object sender, CubeRemovedEventArgs e);
		public class CubeRemovedEventArgs : EventArgs
		{
			public int index;
			public CubeRemovedEventArgs(int index)
			{
				this.index = index;
			}
		}
		public event CubeRemovedHandler CubeRemoved;
		protected void OnCubeRemoved(object sender, CubeRemovedEventArgs e)
		{
			if (CubeRemoved != null)
			{
				CubeRemoved(this, e);
			}
		}

		public ObstacleManager(Effect effect, float cellSize, int numTunnelCells, Tunnel tunnel, GameLogic logic, EmitterFactory emitter)
		{
			this.effect = effect;
			this.cellSize = cellSize;
			this.numTunnelCells = numTunnelCells;
			this.rand = new Random();
			this.cubes = new List<Cube>();
			this.currentPhase = 0.0f;
			this.deltaPhase = 0.0f;
			this.spawnDistance = 20;
			this.cellsTravelled = 0;
			this.spawnFreq = 16;
			this.tunnelRadius = 10.0f;
			this.chaos = 0.0f;
			this.numChaosLevels = 4;
			this.lastCubePos = -1;
			this.logic = logic;
			this.emitter = emitter;

			this.FillColours();
			this.FillChaosLevels();

			tunnel.TunnelSectionCreated += new Tunnel.TunnelSectionCreatedHandler(tunnel_TunnelSectionCreated);
			tunnel.TunnelSectionSetZCalled += new Tunnel.TunnelSectionSetZCalledHandler(tunnel_TunnelSectionSetZCalled);
		}

		void tunnel_TunnelSectionCreated(object o, Tunnel.TunnelSectionCreatedEventArgs e)
		{
			for (int i = 0; i < e.tunnelSection.TunnelLengthInCells; i++)
			{
				int cubePos = 0;
				do { cubePos = rand.Next(0, e.tunnelSection.NumSegments); } while (cubePos == this.lastCubePos);
				this.lastCubePos = cubePos;
				float angle = ((float)(2 * Math.PI) / (float)e.tunnelSection.NumSegments) * cubePos;
				angle += (float)Math.PI / (float)e.tunnelSection.NumSegments;
				if (i % spawnFreq == 0 && GlobalGameStates.GameState == Data.GameState.InGame)
				{
					Cube cube = new Cube(e.tunnelSection.CellSize, GetRandomColour());
					Matrix transform = Matrix.CreateTranslation(new Vector3(0.0f, (float)Math.Sqrt(e.tunnelSection.Radius * e.tunnelSection.Radius - (this.cellSize / 2) * (this.cellSize / 2)) - this.cellSize / 2, (float)i * -e.tunnelSection.CellSize + e.tunnelSection.CellSize/2)) * Matrix.CreateRotationZ(angle);
					for (int j = 0; j <= cube.vertices.GetUpperBound(0); j++)
					{
						cube.vertices[j].Position = Vector3.Transform(cube.vertices[j].Position, transform);
						cube.vertices[j].Position += new Vector3(Tunnel.GetTunnelOffset(e.tunnelSection.PhaseAtStart - cube.vertices[j].Position.Z), 0.0f);
					}
					cubes.Add(cube);
					this.OnCubeCreated(this, new CubeCreatedEventArgs(angle, (float)i * -e.tunnelSection.CellSize, e.tunnelSection.CellSize, e.tunnelSection.NumSegments, cubePos));
				}
			}
		}

		private void FillChaosLevels()
		{
			this.chaosLevels = new int[this.numChaosLevels];
			for (int i = 0; i < this.numChaosLevels; i++)
			{
				this.chaosLevels[i] = (int)Math.Pow(2, i + 1);
			}
		}

		private void SetSpawnFrequency(float chaos)
		{
			this.spawnFreq = this.chaosLevels[(int)((1.0f - chaos) * (this.numChaosLevels - 1))];
		}

		void tunnel_TunnelSectionSetZCalled(object o, Tunnel.TunnelSectionSetZCalledEventArgs e)
		{
			this.SetZ(e.tunnelSection.ZAtStart);
		}

		public void SetZ(float z)
		{
			foreach (Cube cube in this.cubes)
			{
				if (cube.zUnset)
				{
					for (int i = 0; i <= cube.vertices.GetUpperBound(0); i++)
					{
						cube.vertices[i].Position.Z += z;
						cube.zUnset = false;
					}
				}
			}
		}

		public void RemoveCube(int index)
		{
			cubes.RemoveAt(index);
			this.OnCubeRemoved(this, new CubeRemovedEventArgs(index));
		}

		private void CullObjects()
		{
			for (int i = 0; i < this.cubes.Count(); i++ )
			{
				Cube cube = this.cubes[i];
				bool shouldBeCulled = false;
				foreach (VertexPositionColorTexture vert in cube.vertices)
				{
					shouldBeCulled =  shouldBeCulled || vert.Position.Z > 0.0f;
				}
				if (shouldBeCulled)
				{
					this.RemoveCube(i);
				}
			}
		}

        private void SmartBomb(GameTime gameTime)
        {
            //for (int i = 0; i < this.cubes.Count(); i++)
            //{
            //    if (this.cubes[i].vertices[0].Position.Z > -400.0f) this.RemoveCube(i);
            //}

            while (cubes.Count != 0)
            {
				Cube cube = cubes[cubes.Count - 1];
				emitter.CreateExplosionEmitter(cube.Centre, new Color(cube.colour.X, cube.colour.Y, cube.colour.Z), gameTime);
                this.RemoveCube(cubes.Count - 1);
            }
        }

        private void FillColours()
        {
            this.colours = new List<Vector4>();
            this.colours.Add(new Vector4(0.992f, 0.910f, 0.310f, 1.000f));
            this.colours.Add(new Vector4(0.988f, 0.682f, 0.247f, 1.000f));
            this.colours.Add(new Vector4(0.918f, 0.725f, 0.427f, 1.000f));
            this.colours.Add(new Vector4(0.541f, 0.886f, 0.204f, 1.000f));
            this.colours.Add(new Vector4(0.443f, 0.624f, 0.820f, 1.000f));
            this.colours.Add(new Vector4(0.682f, 0.494f, 0.651f, 1.000f));
            this.colours.Add(new Vector4(0.949f, 0.153f, 0.168f, 1.000f));
        }

		private Vector4 GetRandomColour()
		{
			int range = this.colours.Count;
			return this.colours[rand.Next(0, range)];
		}

		public void Update(GameTime gameTime, Tunnel tunnel, float chaos)
		{
			this.deltaPhase = tunnel.DeltaPhase(gameTime);
			this.currentPhase = tunnel.CurrentPhase;

			foreach (Cube cube in cubes)
				cube.Update(gameTime, this.deltaPhase);

			if (logic.DoFireSmartBomb)
			{
				this.SmartBomb(gameTime);
				logic.OnSmartBombHandled();
			}

			this.CullObjects();
			this.SetSpawnFrequency(chaos);
		}

		public void Draw(GraphicsDeviceManager Device, Matrix World, Matrix View, Matrix Projection, float FarClip)
		{
			foreach (Cube cube in cubes)
				cube.Draw(Device, World, View, Projection, FarClip, effect);
		}
	}
}
